package com.strato.mvc.gateway.v1_0_0.impl;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.osgi.framework.BundleContext;

import com.strato.annotation.base.v1_0_0.lifecycle.BundleContextAware;
import com.strato.annotation.base.v1_0_0.registry.BootStateful;
import com.strato.annotation.osgi.v1_0_0.service.OsgiService;
import com.strato.base.cache.v1_0_0.TimedReference;
import com.strato.base.osgi.v1_0_0.BundleUtil;
import com.strato.base.osgi.v1_0_0.Ordered;
import com.strato.mvc.api.v1_0_0.control.HttpInvoker;
import com.strato.mvc.api.v1_0_0.control.PathVariableCollector;
import com.strato.mvc.api.v1_0_0.control.RequestMappingRegistry;
import com.strato.mvc.api.v1_0_0.model.ModelAndView;
import com.strato.mvc.api.v1_0_0.view.ModelEnhancer;
import com.strato.mvc.api.v1_0_0.view.View;
import com.strato.mvc.api.v1_0_0.view.ViewName;
import com.strato.mvc.api.v1_0_0.view.ViewResolver;
import com.strato.mvc.gateway.v1_0_0.GatewayServlet;

@OsgiService
public class DefaultGatewayServlet implements GatewayServlet,BundleContextAware {

	@Override
	public void service(HttpServletRequest request, HttpServletResponse response) throws Exception{
		waitForReady(20*1000);
		RequestMappingRegistry registry=RequestMappingRegistry.getInstance();
		String uri=request.getRequestURI();
		String context=request.getContextPath();
		uri=uri.substring(context.length());
		PathVariableCollector pvc=new PathVariableCollector();
		HttpInvoker invoker = registry.getMappedInvoker(uri,pvc);
		if(invoker==null){
			try {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
			} catch (IOException e) {
				e.printStackTrace();
			}
			return;
		}
		ModelAndView mav=invoker.invoke(request,response,pvc);
		Locale locale=Locale.getDefault();
		View view=resolveView(mav,locale);
		if(view!=null){
			{
				Enumeration<String> names = request.getAttributeNames();
				Map<Object,Object> model=mav.getModel();
				while(names.hasMoreElements()){
					String name=names.nextElement();
					Object attr=request.getAttribute(name);
					model.put(name,attr);
				}
			}
			Map<Object, Object> model = mav.getModel();
			if(model==null){
				model=new HashMap<Object,Object>();
			}
			enhanceModel(model);
			view.render(request, response, model);
		}
	}

	private void waitForReady(int patience) {
		long startTime=System.currentTimeMillis();
		BundleContext bundleContext = bundleContextRef.get();
		List<BootStateful> boots = BundleUtil.getServices(bundleContext, BootStateful.class, null);
		boolean ready=boots==null;
		while(!ready){
			boolean tmpReady=true;
			for(BootStateful boot:boots){
				tmpReady=tmpReady&&boot.isReady();
				if(!tmpReady){
					break;
				}
			}
			ready=tmpReady;
			if(ready){
				return;
			}
			if(System.currentTimeMillis()-startTime<patience){
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return;
				}
			}else{
				throw new RuntimeException("Sytem is not ready");
			}
		}
	}

	private void enhanceModel(Map<Object, Object> model) {
		List<ModelEnhancer> enhancers = BundleUtil.getCachedServices(bundleContextRef.get(), ModelEnhancer.class, null);
		if(enhancers!=null){
			for(ModelEnhancer enhancer:enhancers){
				enhancer.enhance(model);
			}
		}
	}

	private View resolveView(ModelAndView mav, Locale locale) {
		if(mav==null){
			return null;
		}
		View view=mav.getView();
		if(view!=null){
			return view;
		}
		ViewName vn=mav.getViewName();
		if(vn==null){
			return null;
		}
		List<ViewResolver> resolvers = getResolvers();
		String r=vn.getResolver();
		if(r==null){
			for(ViewResolver resolver:resolvers){
				view=resolver.resolveView(vn,locale);
				if(view!=null){
					return view;
				}
			}
		}else{
			for(ViewResolver resolver:resolvers){
				if(resolver.getName().equals(r)){
					view=resolver.resolveView(vn,locale);
					if(view!=null){
						return view;
					}
				}
			}
		}
		throw new RuntimeException("No view found : "+vn);
	}

	private TimedReference<List<ViewResolver>> resolversRef;
	private WeakReference<BundleContext> bundleContextRef;
	
	private List<ViewResolver> getResolvers() {
		if(resolversRef!=null&&!resolversRef.isExpired()){
			List<ViewResolver> o = resolversRef.getObject();
			if(o!=null){
				return o;
			}
		}
		List<ViewResolver> resolvers = BundleUtil.getServices(bundleContextRef.get(), ViewResolver.class, null);
		if(resolvers==null){
			resolversRef=null;
			return null;
		}else{
			Collections.sort(resolvers,Ordered.ASCEND);
			resolversRef=new TimedReference<List<ViewResolver>>(resolvers);
			return resolvers;
		}
	}

	@Override
	public void setBundleContext(WeakReference<BundleContext> context) {
		this.bundleContextRef=context;
	}
}
